package com.dd.cloud.user.service.open.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.cloud.common.config.servlet.JwtUtils;
import com.dd.cloud.common.constants.PharConstants;
import com.dd.cloud.common.exception.ZekeOpenApiException;
import com.dd.cloud.common.utils.AESUtil;
import com.dd.cloud.user.DTO.common.*;
import com.dd.cloud.user.DTO.kz.KzSyncPharmacyDataDTO;
import com.dd.cloud.user.entity.mechan.MechanBindPhar;
import com.dd.cloud.user.entity.mechan.MechanRank;
import com.dd.cloud.user.entity.phar.Pharmacy;
import com.dd.cloud.user.entity.phar.PharmacyDocking;
import com.dd.cloud.user.service.mechan.IMechanBindPharService;
import com.dd.cloud.user.service.mechan.IMechanRankService;
import com.dd.cloud.user.service.open.IOpenApiService;
import com.dd.cloud.user.service.phar.IPharmacyDockingService;
import com.dd.cloud.user.service.phar.IPharmacyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.dd.cloud.common.constants.RedisKeyConstants.OPEN_API_MCH_TOKEN_KEY;
import static com.dd.cloud.common.utils.AESUtil.OPEN_API_KEY;

/**
 * @author Lx
 * @version 1.0
 * @date 2023/8/17
 */
@Slf4j
@Service
public class OpenApiServiceImpl implements IOpenApiService {
    @Autowired
    private JwtUtils jwtUtils;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private IMechanRankService mechanRankService;
    @Autowired
    private IMechanBindPharService mechanBindPharService;
    @Autowired
    private IPharmacyService pharmacyService;
    @Autowired
    private IPharmacyDockingService pharmacyDockingService;

    /**
     * 获取授权token
     *
     * @param dto
     * @return
     */
    @Override
    public CommonResDTO<ResultAccessTokenDTO> getAccessToken(GetAccessTokenDTO dto) {
        CommonResDTO<ResultAccessTokenDTO> res = new CommonResDTO<>();
        ResultAccessTokenDTO resDto = new ResultAccessTokenDTO();
        res.setData(resDto);
        QueryWrapper<MechanRank> q1 = new QueryWrapper<>();
        q1.lambda().eq(MechanRank::getSecretKey, dto.getSecretKey());
        q1.lambda().select(MechanRank::getId, MechanRank::getMchId, MechanRank::getSecretKey);
        MechanRank mechanRank = mechanRankService.getOne(q1);
        if (mechanRank == null) {
            throw new ZekeOpenApiException("400", "机构秘钥不存在/机构秘钥错误");
        }
        ResultAccessTokenDTO.AccessTokenData tokenData = new ResultAccessTokenDTO.AccessTokenData();
        tokenData.setMchId(mechanRank.getMchId());
        tokenData.setSecretKey(mechanRank.getSecretKey());
        tokenData.setMechanId(mechanRank.getId());
        //生成token
        String token = jwtUtils.generateOpenApiToken(JSON.parseObject(JSON.toJSONString(tokenData)));
        String key = OPEN_API_MCH_TOKEN_KEY + mechanRank.getMchId();
        try {
            token = AESUtil.encrypt(token, OPEN_API_KEY);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeOpenApiException("400", "加密系统异常");
        }
        if (token == null) {
            throw new ZekeOpenApiException("400", "生成token失败");
        }
        redisTemplate.opsForValue().set(key, token, 4L, TimeUnit.HOURS);
        resDto.setToken(token);
        return res;
    }

    /**
     * 验证加密数据正确性
     *
     * @param dto
     * @return
     */
    @Override
    public CommonResDTO<String> verifyEncryptionData(VerifyEncryptionDataDTO dto) {
        CommonResDTO<String> res = new CommonResDTO<>();
        try {
            String data = AESUtil.decrypt(dto.getEncryptionData(), dto.getSecretKey());
            log.info("data:{}", data);
            res.setData(data);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeOpenApiException("400", "加密系统异常");
        }
        return res;
    }

    /**
     * 生成加密数据
     *
     * @param dto
     * @return
     */
    @Override
    public CommonResDTO<String> generateEncryptionData(GenerateEncryptionDataDTO dto) {
        CommonResDTO<String> res = new CommonResDTO<>();
        try {
            String data = AESUtil.encrypt(dto.getData(), dto.getSecretKey());
            log.info("data:{}", data);
            res.setData(data);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeOpenApiException("400", "加密系统异常");
        }
        return res;
    }

    /**
     * 上传同步药房门店数据
     *
     * @param request
     * @param dto
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResDTO<String> kzSyncPharmacyData(HttpServletRequest request, KzSyncPharmacyDataDTO dto) {
        String token = request.getHeader("accessToken");
        ResultAccessTokenDTO.AccessTokenData tokenData = getTokenDataAndVerifyToken(token);
        if (dto.getMchId().equals(tokenData.getMchId())) {
            throw new ZekeOpenApiException("400", "token信息和请求参数mchId不匹配");
        }
        String encryptionData = dto.getEncryptionData();
        dto.setEncryptionData(null);
        String encryption;
        try {
            encryption = AESUtil.encrypt(JSON.toJSONString(dto), tokenData.getSecretKey());
        } catch (Exception e) {
            throw new ZekeOpenApiException("400", "加密系统异常");
        }
        if (!Objects.equals(encryption, encryptionData)) {
            throw new ZekeOpenApiException("400", "加密数据被篡改");
        }
        QueryWrapper<Pharmacy> qw = new QueryWrapper<>();
        for (KzSyncPharmacyDataDTO.KzSyncPharmacyData item : dto.getPharmacyDataList()) {
            qw.clear();
            qw.lambda().eq(Pharmacy::getStoreId, item.getStoreId());
            qw.lambda().select(Pharmacy::getId);
            Pharmacy pharmacy = pharmacyService.getOne(qw);
            int add = 0;
            if (pharmacy == null) {
                add = 1;
                pharmacy = new Pharmacy();
                pharmacy.setCreateDate(LocalDateTime.now());
                pharmacy.setIsPay(0);
                pharmacy.setIsRegionLimit(1);
                pharmacy.setStatus(1);
                pharmacy.setFlag(PharConstants.Flag.COMMON);
            }
            pharmacy.setStoreId(item.getStoreId());
            pharmacy.setName(item.getName());
            pharmacy.setProvince(item.getProvince());
            pharmacy.setCity(item.getCity());
            pharmacy.setDistrict(item.getDistrict());
            pharmacy.setAddr(item.getAddr());
            pharmacy.setAdminPhone(item.getAdminPhone());
            pharmacy.setAdminName(item.getAdminName());
            pharmacy.setAdminEmail(item.getAdminEmail());
            pharmacy.setRemark(item.getRemark());
            pharmacy.setType(item.getType());
            pharmacy.setUpdateDate(LocalDateTime.now());
            if (add == 1) {
                pharmacyService.save(pharmacy);
                MechanBindPhar mbp = new MechanBindPhar();
                mbp.setMechanId(tokenData.getMechanId());
                mbp.setPharId(pharmacy.getId());
                mbp.setStatus(1);
                mbp.setCreateDate(LocalDateTime.now());
                mechanBindPharService.save(mbp);
                PharmacyDocking pd = new PharmacyDocking();
                pd.setPharId(pharmacy.getId());
            } else {
                pharmacyService.updateById(pharmacy);
            }
        }
        CommonResDTO<String> res = new CommonResDTO<>();
        res.setData("success");
        return res;
    }

    /**
     * 获取token中的数据并验证token有效性
     *
     * @param token
     * @return
     */
    public ResultAccessTokenDTO.AccessTokenData getTokenDataAndVerifyToken(String token) {
        if (token == null) {
            throw new ZekeOpenApiException("400", "请求未携带accessToken");
        }
        String dToken;
        ResultAccessTokenDTO.AccessTokenData data;
        try {
            dToken = AESUtil.decrypt(token, OPEN_API_KEY);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeOpenApiException("400", "解密系统异常");
        }
        boolean flag;
        try {
            flag = jwtUtils.isTokenExpireDate(dToken);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeOpenApiException("400", "accessToken异常");
        }
        if (flag) {
            throw new ZekeOpenApiException("400", "accessToken过期");
        }
        try {
            data = jwtUtils.getOpenApiTokenData(dToken).toJavaObject(ResultAccessTokenDTO.AccessTokenData.class);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeOpenApiException("400", "accessToken被篡改");
        }
        String key = OPEN_API_MCH_TOKEN_KEY + data.getMchId();
        String redisToken = redisTemplate.opsForValue().get(key);
        log.info("token:{},redisToken:{},dToken:{}", token, redisToken, dToken);
        if (!Objects.equals(token, redisToken)) {
            throw new ZekeOpenApiException("400", "accessToken失效");
        }
        return data;
    }
}
